﻿// The Nova Project by Ken Beckett.
// Copyright (C) 2007-2012 Inevitable Software, all rights reserved.
// Released under the Common Development and Distribution License, CDDL-1.0: http://opensource.org/licenses/cddl1.php

using System;
using System.Collections.Generic;
using System.Reflection;
using System.Windows.Media;

using Nova.CodeDOM;
using Attribute = Nova.CodeDOM.Attribute;

namespace Nova.UI
{
    /// <summary>
    /// The view model for a <see cref="CodeDOM.Attribute"/>.
    /// </summary>
    public class AttributeVM : AnnotationVM
    {
        #region /* STATICS */

        internal static void AddViewModelMapping()
        {
            CreateViewModel.Add(typeof(Attribute),
                delegate(CodeObject codeObject, bool isDescription, Dictionary<CodeObject, CodeObjectVM> dictionary) { return new AttributeVM((Attribute)codeObject, dictionary); });
        }

        #endregion

        #region /* FIELDS */

        protected ChildListVM<ExpressionVM> _attributeExpressionVMs;

        #endregion

        #region /* CONSTRUCTORS */

        /// <summary>
        /// Create a view model instance for the specified <see cref="CodeDOM.Attribute"/>.
        /// </summary>
        public AttributeVM(Attribute attribute, Dictionary<CodeObject, CodeObjectVM> dictionary)
            : base(attribute, dictionary)
        {
            _attributeExpressionVMs = CreateListVM<Expression, ExpressionVM>(attribute.AttributeExpressions, dictionary);
        }

        #endregion

        #region /* PROPERTIES */

        /// <summary>
        /// The underlying <see cref="CodeDOM.Attribute"/> model.
        /// </summary>
        public Attribute Attribute
        {
            get { return (Attribute)CodeObject; }
        }

        /// <summary>
        /// The list of attribute <see cref="Expression"/>s.
        /// </summary>
        public ChildListVM<ExpressionVM> AttributeExpressions
        {
            get { return _attributeExpressionVMs; }
        }

        /// <summary>
        /// True if there are any attribute <see cref="Expression"/>s.
        /// </summary>
        public bool HasAttributeExpressions
        {
            get { return (_attributeExpressionVMs != null && _attributeExpressionVMs.Count > 0); }
        }

        #endregion

        #region /* METHODS */

        #endregion

        #region /* RENDERING */

        public static readonly Brush StaticBorderBrush = Brushes.LightPink;
        public static readonly Brush StaticBackgroundBrush = Brushes.MistyRose;

        public override Brush BorderBrush
        {
            get { return StaticBorderBrush; }
        }

        public override Brush BackgroundBrush
        {
            get { return StaticBackgroundBrush; }
        }

        public override void Render(CodeRenderer renderer, RenderFlags flags)
        {
            int newLines = Attribute.NewLines;
            bool isPrefix = flags.HasFlag(RenderFlags.IsPrefix);
            if (!isPrefix && newLines > 0)
            {
                if (!flags.HasFlag(RenderFlags.SuppressNewLine))
                    renderer.NewLines(newLines, ParentVM);
            }
            else if (flags.HasFlag(RenderFlags.PrefixSpace))
                renderer.RenderText(" ", PUNC_BRUSH, ParentVM);

            bool increaseIndent = flags.HasFlag(RenderFlags.IncreaseIndent);
            if (!flags.HasFlag(RenderFlags.NoBorder))
            {
                CreateBorder(renderer, flags);
                increaseIndent = true;  // Always increase the indent level for wrapped lines inside a border
            }

            RenderFlags passFlags = (flags & RenderFlags.PassMask);
            RenderBefore(renderer, passFlags | RenderFlags.IsPrefix);
            renderer.RenderText(Attribute.ParseTokenStart, PUNC_BRUSH, this);

            // Increase the indent level for any newlines that occur within the declaration if the flag is set
            if (increaseIndent)
                renderer.IndentOnNewLineBegin(this);

            if (Attribute.Target != AttributeTarget.None)
            {
                renderer.RenderText(AttributeTargetHelpers.AsString(Attribute.Target), KEYWORD_BRUSH, this);
                renderer.RenderText(Attribute.ParseTokenTarget + " ", PUNC_BRUSH, this);
            }
            renderer.RenderList(_attributeExpressionVMs, passFlags | RenderFlags.Attribute, this);

            if (increaseIndent)
                renderer.IndentOnNewLineEnd(this);

            renderer.RenderText(Attribute.ParseTokenEnd, PUNC_BRUSH, this);
            RenderEOLComments(renderer, flags);
            RenderAfter(renderer, passFlags | (flags & RenderFlags.NoPostAnnotations));

            // If we have a border, return to the previous one
            if (!flags.HasFlag(RenderFlags.NoBorder))
                renderer.ReturnToBorderParent(this);

            if (isPrefix)
            {
                // If this object is rendered as a child prefix object of another, then any whitespace is
                // rendered here *after* the object instead of before it.
                if (newLines > 0)
                    renderer.NewLines(newLines, ParentVM);
                else
                    renderer.RenderText(" ", PUNC_BRUSH, ParentVM);
            }
        }

        /// <summary>
        /// Helper method to render a collection of Attributes.
        /// </summary>
        public static void RenderAttributes(CodeRenderer renderer, ChildListVM<AttributeVM> attributes, RenderFlags flags)
        {
            if (attributes != null && attributes.Count > 0)
            {
                flags &= ~RenderFlags.Description;  // Don't pass description flag through
                foreach (AttributeVM attrDecl in attributes)
                    attrDecl.Render(renderer, flags);
            }
        }

        public static void RenderAttributes(CodeRenderer renderer, MemberInfo memberInfo, CodeObjectVM tag, AttributeTarget attributeTarget)
        {
            RenderAttributes(renderer, CustomAttributeData.GetCustomAttributes(memberInfo), tag, attributeTarget);
        }

        public static void RenderAttributes(CodeRenderer renderer, MemberInfo memberInfo, CodeObjectVM tag)
        {
            RenderAttributes(renderer, memberInfo, tag, AttributeTarget.None);
        }

        public static void RenderAttributes(CodeRenderer renderer, ParameterInfo parameterInfo, CodeObjectVM tag, AttributeTarget attributeTarget)
        {
            RenderAttributes(renderer, CustomAttributeData.GetCustomAttributes(parameterInfo), tag, attributeTarget);
        }

        public static void RenderAttributes(CodeRenderer renderer, ParameterInfo parameterInfo, CodeObjectVM tag)
        {
            RenderAttributes(renderer, parameterInfo, tag, AttributeTarget.None);
        }

        protected static void RenderAttributes(CodeRenderer renderer, IList<CustomAttributeData> attributes, CodeObjectVM tag, AttributeTarget attributeTarget)
        {
            int count = 0;

            // Use the static method so that this works with types from reflection-only assemblies
            foreach (CustomAttributeData attribute in attributes)
            {
                // Filter out ExtensionAttributes as they are compiler generated, and displayed in a special manner
                Type declaringType = attribute.Constructor.DeclaringType;
                if (declaringType != null)
                {
                    if (count > 0)
                        renderer.RenderText(" ", IDENTIFIER_BRUSH, tag);
                    renderer.CreateBorder(StaticBorderBrush, StaticBackgroundBrush, tag);
                    renderer.RenderText(Attribute.ParseTokenStart, PUNC_BRUSH, tag);
                    if (attributeTarget != AttributeTarget.None)
                    {
                        renderer.RenderText(AttributeTargetHelpers.AsString(attributeTarget), KEYWORD_BRUSH, tag);
                        renderer.RenderText(": ", PUNC_BRUSH, tag);
                    }

                    // Render as ConstructorRefVM for nested tooltips
                    ConstructorRefVM.RenderConstructorInfoAsConstructorRefVM(renderer, attribute.Constructor, RenderFlags.Attribute, tag);

                    if (attribute.ConstructorArguments.Count > 0)
                    {
                        renderer.RenderText(ParameterDecl.ParseTokenStart, PUNC_BRUSH, tag);
                        bool isFirst = true;
                        foreach (CustomAttributeTypedArgument argument in attribute.ConstructorArguments)
                        {
                            if (!isFirst)
                                renderer.RenderText(", ", PUNC_BRUSH, tag);
                            RenderValue(renderer, argument.Value, tag);
                            isFirst = false;
                        }
                        renderer.RenderText(ParameterDecl.ParseTokenEnd, PUNC_BRUSH, tag);
                    }
                    else if (attribute.NamedArguments != null && attribute.NamedArguments.Count > 0)
                    {
                        renderer.RenderText(ParameterDecl.ParseTokenStart, PUNC_BRUSH, tag);
                        bool isFirst = true;
                        foreach (CustomAttributeNamedArgument argument in attribute.NamedArguments)
                        {
                            if (!isFirst)
                                renderer.RenderText(", ", PUNC_BRUSH, tag);
                            renderer.RenderText(argument.MemberInfo.Name, IDENTIFIER_BRUSH, tag);
                            renderer.RenderText(" ", PUNC_BRUSH, tag);
                            AssignmentVM.RenderOperator(renderer, OPERATOR_BRUSH, tag);
                            renderer.RenderText(" ", PUNC_BRUSH, tag);
                            RenderValue(renderer, argument.TypedValue.Value, tag);
                            isFirst = false;
                        }
                        renderer.RenderText(ParameterDecl.ParseTokenEnd, PUNC_BRUSH, tag);
                    }

                    renderer.RenderText(Attribute.ParseTokenEnd, PUNC_BRUSH, tag);

                    renderer.ReturnToBorderParent(tag);
                    ++count;
                }
            }
            if (count > 0)
                renderer.NewLine();
        }

        protected static void RenderAttributes(CodeRenderer renderer, IList<CustomAttributeData> attributes, CodeObjectVM tag)
        {
            RenderAttributes(renderer, attributes, tag, AttributeTarget.None);
        }

        private static void RenderValue(CodeRenderer renderer, object value, CodeObjectVM tag)
        {
            CodeObject parent = (tag != null ? tag.CodeObject : null);
            if (value is Type)
            {
                TypeOf typeOf = new TypeOf(TypeRef.CreateNested((Type)value)) { Parent = parent };
                CreateVM(typeOf, tag).Render(renderer, RenderFlags.ForceBorder);
            }
            else
            {
                if (value is Array)
                {
                    Initializer initializer = new Initializer { Parent = parent };
                    foreach (object element in (Array)value)
                        initializer.CreateExpressions().Add(new Literal(element));
                    CreateVM(initializer, tag).Render(renderer, RenderFlags.ForceBorder);
                }
                else
                {
                    Literal literal = new Literal(value) { Parent = parent };
                    CreateVM(literal, tag).Render(renderer, RenderFlags.ForceBorder);
                }
            }
        }

        public override void UnRender()
        {
            ChildListHelpers.UnRender(_attributeExpressionVMs);
            base.UnRender();
        }

        #endregion
    }
}
